﻿@using System.IO;
@using System.Text;
@using System.Text.Json;
@using System.Text.RegularExpressions;
@using simu485.Data;
@inject ToastService ToastService;
@inject DialogService DialogService;

<div class="row">
    <div class="col-2"> <h5>@title</h5> </div>
    <div class ="col-8"> <h5>@last_err</h5> </div>
    <div class="col-1">
        <Button class="btn btn-primary" @onclick="btn_load_excel" Size="Size.Small"> 加载命令 </Button>
    </div>
    <div class="col-1">
        <Button class="btn btn-primary" @onclick="btn_save_excel" Size="Size.Small"> 保存命令 </Button>
    </div>
</div>

<div> 
    <Table @ref="@CmdTable" TItem="C485CmdItem" Items="@Items?.Take(Items.Count)"
           IsStriped="true" IsBordered="true" AllowResizing="true" IsFixedHeader="true" Height="550"
           ClickToSelect="true" OnSaveAsync="@OnSaveAsync" OnDoubleClickCellCallback="@OnDoubleClickCellCallback">
        @* DoubleClickToEdit="true"*@
        <TableColumns>
            <TableColumn @bind-Field="@context.cmd_code" Text="指令码" Align="Alignment.Center" Width="100" Readonly="true" OnCellRender="@OnCmdRenderHandler" />
            <TableColumn @bind-Field="@context.des" Text="指令描述" Align="Alignment.Center" Width="180" Readonly="true" />
            <TableColumn @bind-Field="@context.value" Text="指令值" Align="Alignment.Center" Width="80" />
            <TableColumn @bind-Field="@context.send" Text="发送数据" Align="Alignment.Left" TextEllipsis="true" ShowTips="true" />
            <TableColumn @bind-Field="@context.recv" Text="接收数据" Align="Alignment.Left" Readonly="true" TextEllipsis="true" ShowTips="true" />
            <TableColumn @bind-Field="@context.error" Text="成功/失败" Align="Alignment.Center" Width="180" Readonly="true" />
            <TableColumn @bind-Field="@context.cmd_code" Text="发送" Width="80" Readonly="true">
                <Template Context="value">
                    <Button Color="Color.Primary" Text="发送" Size="Size.ExtraSmall" @onclick="() => btn_send_click(value.Row)" IsDisabled = "@(cur_item != null)"/>
                </Template>
            </TableColumn>
        </TableColumns>
    </Table>
</div>

@code {

    // private 
    public class C485CmdItem
    {
        public ushort cmd_code { get; set; } = 0;  // 类似：cmd0203这样
        public string des { get; set; } = "";       // 类似：库设置温度设定
        public string value { get; set; } = "";     // 类似：库设置温度设定
        public string send { get; set; } = "";      // 发送的字符串：DD 00 09 01 02 03 01 01 01 00 01 50 59 88
        public string recv { get; set; } = "";      // 接收到的应答：DD 00 09 02 02 03 01 01 01 00 01 50 53 88
        public string error { get; set; } = "";     // 类似：数据接收超时

        public int value_type = 0;
        public int state = 0;                       // 追踪发送状态。
        public int resend_time { get; set; } = 0;   // 发送次数（>1 表示重试了）。
    }
    public Table<C485CmdItem>? CmdTable { get; set; } // table 控件
    public List<C485CmdItem> Items { get; set; } = new();
    private string title = "485 Cmd Table";
    private string last_err = "";
    private C485CmdItem? cur_item = null;
    public C485ParamTb? param_tb = null;

    public byte[]? PackSendData(ushort cmd_code, string value, int value_type)
    {
        if (value.Length > 48 || value.Length < 2) // TODO 目前指支持指令值长度小于48，总体长度小于64
            return null;
        byte[]? value_bytes = null;
        if (value_type != 4)
        {
            string[] strs = value.Split(" ");
            if ((value_type == 2 && (strs.Length % 2) != 0) || (value_type == 3 && (strs.Length % 4) != 0))
                return null;
            value_bytes = new byte[strs.Length];
            for (int i = 0; i < value_bytes.Length; i++)
            {
                if (!byte.TryParse(strs[i], System.Globalization.NumberStyles.HexNumber, null, out value_bytes[i]))
                    return null;
            }
        }
        // 开始构造发送数据
        int len = 0;
        byte[] buf = new byte[64];
        // 例子 "DD 00 09 01 02 03 01 01 01 00 01 50 59 88";
        buf[len++] = 0xdd;            // head flag
        buf[len++] = 0x00;            // len high byte
        byte len_low_index = (byte)len;    // record this position
        buf[len++] = 0x00;            // len low byte
        buf[len++] = 0x01;            // mechine number
        buf[len++] = (byte)(cmd_code >> 8); // cmd number
        buf[len++] = (byte)cmd_code;
        buf[len++] = 0x01;            // lib num
        buf[len++] = 0x01;            // block num
        buf[len++] = (byte)value_type;// data type

        buf[len++] = 0x00;                     // data number high byte
        if (value_type == 1)
            buf[len++] = (byte)(value_bytes.Length); // data number low byte
        else if (value_type == 2)
            buf[len++] = (byte)(value_bytes.Length / 2); // data number low byte
        else if (value_type == 3)
            buf[len++] = (byte)(value_bytes.Length / 4); // data number low byte
        else
            buf[len++] = (byte)(value.Length); // data number low byte

        if (value_type == 4)
        {
            foreach (char ch in value)
                buf[len++] = (byte)ch;
        }
        else
        {
            for (int i = 0; i < value_bytes?.Length;)
                buf[len++] = value_bytes[i++];
        }

        buf[len_low_index] = (byte)(len - 3); // 减去head flag, len本身
        byte s = 0;
        for (int i = 1; i < len; i++)
        {
            s ^= buf[i];
        }
        buf[len++] = s; // 预备放checksum
        buf[len++] = 0x88; // tail flag
        return buf[0..len];
    }

    public C485CmdItem CreateItem(ushort cmd_code, string des, string value = "00", int value_type = 1)
    {
        C485CmdItem item = new();
        item.cmd_code = cmd_code;
        item.des = des;
        item.value = value;
        item.value_type = value_type;
        byte[]? buf = PackSendData(cmd_code, value, value_type);
        if (buf != null)
            item.send = BitConverter.ToString(buf).Replace("-", " ");
        else
            item.send = "";
        item.state = 0;

        return item;
    }
    protected override void OnInitialized()
    {
        Items.Add(CreateItem(0x0101, "通信监测"));
        // Items.Add(CreateItem(0x0102, "无效指令应答"));
        // Items.Add(CreateItem(0x0103, "自动发送指令应答"));

        Items.Add(CreateItem(0x0111, "设备重启"));
        // Items.Add(CreateItem(0x0112, "程序升级开始"));
        // Items.Add(CreateItem(0x0113, "程序升级传送"));
        Items.Add(CreateItem(0x0114, "程序版本取得"));
        Items.Add(CreateItem(0x0115, "硬件版本取得"));
        Items.Add(CreateItem(0x0116, "厂家代码取得"));

        Items.Add(CreateItem(0x0121, "设备类型取得"));
        Items.Add(CreateItem(0x0122, "设备功能取得"));
        Items.Add(CreateItem(0x0123, "系统时间设定", "20040201090104", 4));
        Items.Add(CreateItem(0x0124, "系统时间取得"));
        Items.Add(CreateItem(0x0125, "运行状态取得"));
        Items.Add(CreateItem(0x0126, "现在故障解除", "00 00", 2));
        Items.Add(CreateItem(0x0127, "现在故障取得"));
        Items.Add(CreateItem(0x0128, "压缩机实时状态取得"));
        Items.Add(CreateItem(0x0129, "门状态取得"));
        Items.Add(CreateItem(0x012A, "灯状态设定"));
        Items.Add(CreateItem(0x0130, "灯状态取得"));
        Items.Add(CreateItem(0x012B, "功率值取得"));
        Items.Add(CreateItem(0x012C, "电池电压值取得"));
        Items.Add(CreateItem(0x012D, "联网状态取得", "00 12", 2));
        Items.Add(CreateItem(0x012E, "蒸发风机状态设定", "01", 1));
        Items.Add(CreateItem(0x012F, "蒸发风机状态取得"));
        Items.Add(CreateItem(0x0131, "灯电流取得"));
        Items.Add(CreateItem(0x0132, "4G信号值取得", "1D"));



        Items.Add(CreateItem(0x0201, "库制冷/制热模式设定"));
        Items.Add(CreateItem(0x0202, "库制冷/制热模式取得"));

        Items.Add(CreateItem(0x0203, "库设置温度设定"));
        Items.Add(CreateItem(0x0204, "库设置温度取得"));

        Items.Add(CreateItem(0x0205, "库高温报警温度设定"));
        Items.Add(CreateItem(0x0206, "库高温报警温度取得"));
        Items.Add(CreateItem(0x0207, "库低温报警温度设定"));
        Items.Add(CreateItem(0x0208, "库低温报警温度取得"));

        Items.Add(CreateItem(0x0209, "库当前温度取得"));

        Items.Add(CreateItem(0x020A, "库设置温度下限设定"));
        Items.Add(CreateItem(0x020B, "库设置温度下限取得"));
        Items.Add(CreateItem(0x020C, "库设置温度上限设定"));
        Items.Add(CreateItem(0x020D, "库设置温度上限取得"));

        Items.Add(CreateItem(0x020E, "库温度上回差取得"));
        Items.Add(CreateItem(0x020F, "库温度下回差取得"));

        Items.Add(CreateItem(0x0210, "库传感器故障压缩机开机时间取得"));
        Items.Add(CreateItem(0x0211, "库传感器故障压缩机关机时间取得"));

        Items.Add(CreateItem(0x0212, "库温度补偿值设定"));
        Items.Add(CreateItem(0x0213, "库温度补偿值取得"));

        Items.Add(CreateItem(0x0214, "库除霜周期/压缩机保护周期取得"));
        Items.Add(CreateItem(0x0215, "库除霜时间/压缩机保护时间取得"));
        Items.Add(CreateItem(0x0216, "库压缩机最小停机时间取得"));

        Items.Add(CreateItem(0x0217, "库环境温度取得"));
        Items.Add(CreateItem(0x0218, "库实际温度取得"));
        Items.Add(CreateItem(0x0219, "库本地控制状态取得"));

        Items.Add(CreateItem(0x0501, "开门次数清空"));
        Items.Add(CreateItem(0x0502, "开门次数取得"));
        Items.Add(CreateItem(0x0503, "客流次数清空"));
        Items.Add(CreateItem(0x0504, "客流次数取得"));

        base.OnInitialized();
    }

    public C485Uart? uart;
    private void Send(string data)
    {
        string[] hexValues = data.Split(' ');
        byte[] bytes = new byte[hexValues.Length];
        for (int i = 0; i < hexValues.Length; i++)
        {
            bytes[i] = Convert.ToByte(hexValues[i], 16);
        }
        uart!.Send(bytes);
    }
    private void btn_send_click(C485CmdItem item)
    {
        if (uart == null || cur_item != null)
            return;
        cur_item = item;
        Send(item.send);
        StateHasChanged();
    }


    // 自身改变数据, 记录
    private Task<bool> OnSaveAsync(C485CmdItem item, ItemChangedType changedType)
    {
        return Task.FromResult(true);
    }


    public class PopEditData
    {
        // pop 显式的值
        public string value { get; set; } = "";
        // 后台记录的值
        public C485CmdItem item;
    }
    private PopEditData Model { get; set; } = new PopEditData();
    private async Task OnDoubleClickCellCallback(string columnName, object row, object value)
    {
        if (columnName != "value")
            return;
        C485CmdItem item = (C485CmdItem)row;
        Model.value = item.value;
        Model.item = item;

        var option = new EditDialogOption<PopEditData>()
            {
                Title = "参数修改",
                Size = Size.Medium,
                Model = Model,
                Items = Utility.GenerateEditorItems<PopEditData>(),
                ItemsPerRow = 1,
                RowType = RowType.Inline,
                // OnCloseAsync = () =>
                // {
                //     return Task.CompletedTask;
                // },
                OnEditAsync = context =>
                {
                    PopEditData data = (PopEditData)(context.Model);
                    data.item.value = data.value;
                    byte[]? buf = PackSendData(data.item.cmd_code, data.item.value, data.item.value_type);
                    if (buf != null)
                        data.item.send = BitConverter.ToString(buf).Replace("-", " ");
                    InvokeAsync(StateHasChanged);
                    return Task.FromResult(true);
                }
            };
        await DialogService.ShowEditDialog(option);
    }

    public string RecvResponseValueParse(C485CmdItem item, string value, int value_type)
    {
        if (param_tb == null) return "模拟器初始化-错误";
        string str1 = item.des.Substring(0, item.des.Length - 2);
        string str2 = item.des.Substring(item.des.Length - 2);

        if (str2 == "取得") // 读取型, 有相应绑定参数要处理
        {
            return param_tb.SetValueByDes(str1, value, value_type);
        }
        else if (str2 == "设定") // 设定型, 有相应绑定参数要处理
        {
            if (item.cmd_code == 0x0203 || item.cmd_code == 0x0205 || item.cmd_code == 0x0207
                || item.cmd_code == 0x020A || item.cmd_code == 0x020C || item.cmd_code == 0x0212) // 特定的温度设置命令
            {
                if (item.value != value) return $"回复数据报错:{value}-错误";
            }
            else
            {
                if (value != "01")  // 设定不成功
                    return $"回复数据报错:{value}-错误";
            }
            return param_tb.SetValueByDes(str1, item.value, item.value_type);
        }
        else if (str2 == "解除") // 特定现在故障解除cmd0126, 修改绑定参数
        {
            if (value != "01")  // 设定不成功
                return $"回复数据报错:{value}-错误";
            else
                return param_tb.SetValueDismissFault(item.value, item.value_type);
        }
        else // 其他型
        {
            if (item.cmd_code == 0x0101) return "成功";   // 特例1, 通信监测指令
            if (value != "01") return $"回复数据报错:{value}-错误";
        }
        return "成功";
    }
    public C485CmdItem? GetCmdItemByCode(ushort cmd_code)
    {
        foreach (C485CmdItem item in Items)
        {
            if (item.cmd_code == cmd_code) return item;
        }
        return null;
    }
    public string RecvCmdValueParse(C485CmdItem item, ushort cmd_code, string value, int value_type)
    {
        if (param_tb == null || uart == null) return "模拟器初始化-错误";
        string str1 = item.des.Substring(0, item.des.Length - 2);
        string str2 = item.des.Substring(item.des.Length - 2);

        if (str2 == "取得") // 必须读取型, 且有相应绑定参数要处理
        {
            // 几个特殊命令是反向的，如联网状态取得，4G信号值取得，需要回复具体数据
            if (cmd_code == 0x012D || cmd_code == 0x0132)
            {
                Send(item.send);
                return "成功";
            }
            string err = param_tb.SetValueByDes(str1, value, value_type);
            if (err != "成功") return err;
            byte[] bytes_value = new byte[] { (byte)(cmd_code >> 8), (byte)(cmd_code)};
            string resp_value = BitConverter.ToString(bytes_value).Replace("-", " ");
            byte[]? buf = PackSendData(0x0103, resp_value, 2); // 构造自动指令应答报文
            if (buf != null)
                uart.Send(buf);
        }
        else // 其他型
        {
            return "收到未知指令-错误";
        }
        return "成功";
    }


    private bool ReSend()
    {
        // 处理等待回应是否超时, 是否重发
        if (cur_item != null)
        {
            if (cur_item.state > 5000)
            {
                cur_item.state = 0;
                if (cur_item.error == "") cur_item.error = "接收数据超时-错误"; // 优先显示其他错误
                cur_item = null;
                return true;
            }
            else
            {
                cur_item.state += 50;
                if (cur_item.state == 1000 || cur_item.state == 2000 || cur_item.state == 3000) // TODO resend
                {

                }
            }
        }
        return false;
    }

    // return true - has a update
    public void Update(byte[]? data)
    {
        bool state_has_changed = false;
        // 处理等待回应是否超时, 是否重发
        state_has_changed = ReSend();

        // check received data
        string cur_err = "";
        // 无数据
        if (data == null) goto update_end;
        state_has_changed = true;
        if(cur_item != null)
            cur_item.recv = BitConverter.ToString(data).Replace("-", " ");
        if (data.Length > 64 || data.Length < 14) // 64长度限制
            cur_err = $"接收数据总长度:{data.Length}-错误";
        else if (data[0] != 0xDD || data[data.Length - 1] != 0x88) // 头尾格式错误
            cur_err = "接收数据头尾标志-错误";
        else if (((int)data[1] << 8 | data[2]) != (data.Length - 5))     // pkg length
            cur_err = $"接收数据内部包长度:{((int)data[1] << 8 | data[2]):X02}-错误";
        byte s = 0;
        for (int i = 1; i < (data.Length - 2); i++)
        {
            s ^= data[i];
        }
        if (s != data[data.Length - 2])
            cur_err = $"接收数据校验错误, 接收校验:{data[data.Length - 3]}, 计算校验:{s}-错误";

        if (cur_err != "") goto update_end;
        // further check received cmd and type
        ushort cmd_code = (ushort)(data[4] << 8 | data[5]);
        byte value_type = data[8];
        if (C485Cmds.GetDesByCmd(cmd_code) == "无效指令")
            cur_err = "接收到无法识别指令-错误";
        else if (value_type < 1 || value_type > 4) // undefined type
            cur_err = $"接收数据类型:{value_type:X02}-错误";
        if (cur_err != "") goto update_end;
        // further check value
        int value_num = (int)data[10];
        string value = "";
        // byte[] value = data[11..(data.Length - 2)];
        if (value_type == 4) // string
            value = Encoding.ASCII.GetString(data[11..(data.Length - 2)]);
        else 
            value = BitConverter.ToString(data, 11, (data.Length - 13)).Replace("-", " ");
        if ((value_type == 1 && value.Length != (value_num * 3 - 1))
            || (value_type == 2 && value.Length != (value_num * 2 * 3 - 1))
            || (value_type == 3 && value.Length != (value_num * 4 * 3 - 1))
            || (value_type == 4 && value.Length != (value_num * 1)))
        {
            cur_err = "接收数据值域类型/长度-错误";
        }
        else if (value_type > 1 && value_type < 4)
        {
            string pattern = @"^([0-9A-Fa-f]{2}\s*)+$";
            Match match = Regex.Match(value, pattern);
            if (!match.Success)  // 是16进制数字
                cur_err = $"接收数据类型非数字-错误";
        }
        if (cur_err != "") goto update_end;

        // 分别处理response和ctrl cmd
        if (cur_item != null) // waiting a response
        {
            if (cmd_code == cur_item.cmd_code)    // 收到相应的回复
                cur_err = RecvResponseValueParse(cur_item, value, value_type);
            else if (cmd_code == 0x0102)    // 无效指令
                cur_err = "无效指令-控制器不支持";
            else if ( cmd_code == 0x0103)   // 自动指令回复
            {
                ushort cmd = (ushort)((data[11] << 8) | data[12]);
                if ((cur_item.cmd_code == 0x012D || cur_item.cmd_code == 0x0132) && cur_item.cmd_code == cmd)
                {
                    cur_err = RecvResponseValueParse(cur_item, cur_item.value, cur_item.value_type);
                }
                else
                    cur_err = "接收数据无法识别-错误";
            }
            else
                cur_err = "接收数据无法识别-错误";
        }
        else // received a ctrl actived cmd
        {
            C485CmdItem? item = GetCmdItemByCode(cmd_code);
            if (item == null) 
            {
                cur_err = "收到未知指令-错误";
            }
            else
            {
                item.recv = BitConverter.ToString(data).Replace("-", " ");
                cur_err = RecvCmdValueParse(item, cmd_code, value, value_type);
            }
        }

    update_end:
        last_err = "-- " + cur_err;
        if (cur_err != "")
        {
            if (cur_item != null) cur_item.error = cur_err;
            if (cur_err == "成功" || cur_err == "无效指令-控制器不支持") 
                cur_item = null;
        }
        if (state_has_changed) 
            InvokeAsync(StateHasChanged);
    }

    private static void OnCmdRenderHandler(TableCellArgs args)
    {
        if (args.Row is C485CmdItem item)
        {
            if ( args.ColumnName == "cmd_code")
            {
                args.Value = $"{item.cmd_code:X04}";
            }
        }
    }

    private void btn_load_excel()
    {
        List<C485CmdItem>? tmp = C485Excel.Load("c485_cmd.xlsx");
        if (tmp != null) Items = tmp;
    }
    private void btn_save_excel()
    {
        C485Excel.Save("c485_cmd.xlsx", Items);
    }

}